/* ----------------------------------------------------------------------
 *	Shadow Quest
 *	Copyright (C) 2009 Prageeth Silva <prageeth@thenewcoders.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *  ----------------------------------------------------------------------
 */
package menu;

import java.io.IOException;
import java.util.ArrayList;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;

import menu.MenuButton.ButtonType;

import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.xml.sax.SAXException;

import sq.FileIO;
import sq.FontLoader;
import sq.ImageLoader;
import sq.LoadScript;
import sq.RPG;
import sq.SoundLoader;
import sq.game.Quest;

/** The Menu of The game
 * 
 */
public class Menu {

    protected static String RESOURCE_PATH = "quests/";
    protected static String SCRIPT_NAME = "quests.lua";
    private static final String FUNC_LOAD = "load";
    private static final int BORDER = 100;
    private static final String PRESS_KEY = "Press SPACE key to continue...";
    private Image image;
    private ArrayList<MenuButton> buttons;
    private boolean error, busy, click;
    private ButtonType menuState;
    private String busyMsg, errorMsg;
    private LoadScript script;
    private Quest quest;
    private SubMenu menuNew, menuAbout, menuSettings, menuHelp;
    private RPG rpg;

    /** Create a new Menu object
     *
     */
    public Menu(RPG rpg)
            throws SlickException {
        // create the image
        String dirPath = "assets/menu/";
        this.setRPG(rpg);
        this.buttons = new ArrayList<MenuButton>();
        this.setImage(new Image(dirPath + "menu.jpg"));
        this.setMenuState(ButtonType.NONE);
        this.setBusyMessage("");
        this.setErrorMessage("");
        this.setError(false);
        this.setBusy(false);
        this.setClick(false);

        this.buttons.add(new MenuButton(dirPath + "newgame.jpg", 100, 116, ButtonType.NEW_GAME));
        this.buttons.add(new MenuButton(dirPath + "instructions.jpg", 100, 240, ButtonType.HELP));
        this.buttons.add(new MenuButton(dirPath + "savegame.jpg", 575, 116, ButtonType.SAVE_GAME));
        this.buttons.add(new MenuButton(dirPath + "loadgame.jpg", 575, 240, ButtonType.LOAD_GAME));
        this.buttons.add(new MenuButton(dirPath + "about.jpg", 38, 497, ButtonType.ABOUT));
        this.buttons.add(new MenuButton(dirPath + "settings.jpg", 301, 497, ButtonType.SETTINGS));
        this.buttons.add(new MenuButton(dirPath + "exit.jpg", 557, 481, ButtonType.EXIT));

        try {
            FileIO.loadSubMenus(this, "data/menu.xml");
        } catch (XMLStreamException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }

        this.loadQuests();

    }

    /** Update the menu state for a frame.
     * @param input The input object
     * @param delta Time passed since last frame (milliseconds).
     */
    public void update(Input input, int delta)
            throws SlickException {

        float mousex = input.getMouseX();
        float mousey = input.getMouseY();
        boolean mousedown = input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON);
        boolean mousepress = input.isMousePressed(Input.MOUSE_LEFT_BUTTON);
        boolean anykey = input.isKeyPressed(Input.KEY_SPACE);

        // just return to the menu if an error or busy message is shown
        if (this.isError() || this.isBusy()) {

            if (anykey) {
                this.menuContinue();
            }

        } else if (this.getMenuState() == ButtonType.NONE) {

            for (MenuButton b : this.buttons) {
                b.update(mousex, mousey, mousedown, delta);
            }

            this.setClick(mousepress);
            this.setMenuState(this.isAnyButtonPressed(mousex, mousey));

            if (this.isClicked()) {
                if (this.getMenuState() == ButtonType.NEW_GAME) {
                    this.getMenuNew().open();
                } else if (this.getMenuState() == ButtonType.ABOUT) {
                    this.getMenuAbout().open();
                } else if (this.getMenuState() == ButtonType.HELP) {
                    this.getMenuHelp().open();
                } else if (this.getMenuState() == ButtonType.SETTINGS) {
                    this.getMenuSettings().open();
                }
            }

            // update submenus
        } else if (this.getMenuState() == ButtonType.NEW_GAME) {
            this.getMenuNew().update(input, mousepress, delta);
        } else if (this.getMenuState() == ButtonType.ABOUT) {
            this.getMenuAbout().update(input, mousepress, delta);
        } else if (this.getMenuState() == ButtonType.HELP) {
            this.getMenuHelp().update(input, mousepress, delta);
        } else if (this.getMenuState() == ButtonType.SETTINGS) {
            this.getMenuSettings().update(input, mousepress, delta);
        }

    }

    /** Render the entire screen, so it reflects the current game state.
     * @param g The Slick graphics object, used for drawing.
     */
    public void render(Graphics g)
            throws SlickException {

        Quest q = this.getRPG().getCurrentQuest();

        this.getImage().draw(0f, 0f);

        for (MenuButton b : this.buttons) {
            b.render(g);
        }

        if (q != null && q.isGameStarted() && this.getMenuState() == ButtonType.NONE) {
            g.setFont(FontLoader.getFontSize12());
            g.setColor(Color.white);
            String str = "Press ESCAPE to resume the game.";
            int textWidth = g.getFont().getWidth(str);
            g.drawString(str, (RPG.getScreenWidth() - textWidth) / 2, RPG.getScreenHeight() - 20f);
        }

        if (this.isError()) {

            this.drawErrorScreen(g);

        } else if (this.isBusy()) {

            this.drawBusyScreen(g);

        } else {

            if (this.getMenuState() == ButtonType.NEW_GAME) {
                this.getMenuNew().render(g);
            } else if (this.getMenuState() == ButtonType.ABOUT) {
                this.getMenuAbout().render(g);
            } else if (this.getMenuState() == ButtonType.HELP) {
                this.getMenuHelp().render(g);
            } else if (this.getMenuState() == ButtonType.SETTINGS) {
                this.getMenuSettings().render(g);
            }

        }

    }

    /** Draw the busy screen.
     * @param g The graphics object
     */
    private void drawBusyScreen(Graphics g) {

        float width = RPG.getScreenWidth() - 2 * BORDER, height = RPG.getScreenHeight() - 2 * BORDER;
        float x = (RPG.getScreenWidth() - width) / 2, y = (RPG.getScreenHeight() - height) / 2;
        g.setColor(FontLoader.getDisplayBgColor());
        g.fillRect(x, y, width, height);

        x = (RPG.getScreenWidth() - ImageLoader.getBusyAnimation().getWidth()) / 2;
        y = (RPG.getScreenHeight() - ImageLoader.getBusyAnimation().getHeight()) / 2;
        ImageLoader.getBusyAnimation().draw(x, y);

        width = g.getFont().getWidth(this.getBusyMessage());
        height = g.getFont().getHeight(this.getBusyMessage());

        x = (RPG.getScreenWidth() - width) / 2;
        y += 1.5f * ImageLoader.getBusyAnimation().getHeight();
        g.setColor(Color.white);
        g.drawString(this.getBusyMessage(), x, y);

        width = g.getFont().getWidth(PRESS_KEY);
        x = (RPG.getScreenWidth() - width) / 2;
        y += 2f * height;
        g.drawString(PRESS_KEY, x, y);

    }

    /** Draw the busy screen.
     * @param g The graphics object
     */
    private void drawErrorScreen(Graphics g) {

        g.setFont(FontLoader.getFontSize12());

        float width = RPG.getScreenWidth() - 2 * BORDER, height = RPG.getScreenHeight() - 2 * BORDER;
        float x = (RPG.getScreenWidth() - width) / 2, y = (RPG.getScreenHeight() - height) / 2;
        g.setColor(FontLoader.getDisplayBgColor());
        g.fillRect(x, y, width, height);

        x = (RPG.getScreenWidth() - ImageLoader.getMenuError().getWidth()) / 2;
        y = (RPG.getScreenHeight() - ImageLoader.getMenuError().getHeight()) / 2;
        ImageLoader.getMenuError().draw(x, y);

        width = g.getFont().getWidth(this.getErrorMessage());
        height = g.getFont().getHeight(this.getErrorMessage());
        x = (RPG.getScreenWidth() - width) / 2;
        y += 1.5f * ImageLoader.getMenuError().getHeight();
        g.setColor(Color.white);
        g.drawString(this.getErrorMessage(), x, y);

        width = g.getFont().getWidth(PRESS_KEY);
        x = (RPG.getScreenWidth() - width) / 2;
        y += 2f * height;
        g.drawString(PRESS_KEY, x, y);

    }

    /** Get the display image of the player.
     * @return The Display Image.
     */
    private Image getImage() {
        return this.image;
    }

    /** Set the display image of the player.
     * @param image The Display Image.
     */
    private void setImage(Image image) {
        this.image = image;
    }

    /** Checks if any of the buttons were pressed.
     * @param x The x coordinate of the mouse pointer.
     * @param y The y coordinate of the mouse pointer.
     */
    private ButtonType isAnyButtonPressed(float x, float y) {
        if (this.isClicked()) {
            for (MenuButton b : this.buttons) {
                if (b.isOnButton(x, y)) {
                    return b.getType();
                }
            }
        }
        return ButtonType.NONE;
    }

    /** Show error
     */
    public void gameError(String errorMsg) {
        this.setError(true);
        this.setErrorMessage(errorMsg);
    }

    /** No more error
     */
    public void menuContinue() {
        this.setError(false);
        this.setBusy(false);
    }

    /** Show busy
     */
    public void gameBusy(String busyMsg) {
        this.setBusy(true);
        this.setBusyMessage(busyMsg);
    }

    public ButtonType getMenuState() {
        return this.menuState;
    }

    public void setMenuState(ButtonType menuState) {
        this.menuState = menuState;
    }

    public boolean isError() {
        return this.error;
    }

    public boolean isBusy() {
        return this.busy;
    }

    private String getBusyMessage() {
        return this.busyMsg;
    }

    private void setBusyMessage(String msg) {
        this.busyMsg = msg;
    }

    private void setError(boolean error) {
        this.error = error;
    }

    private void setBusy(boolean busy) {
        this.busy = busy;
    }

    private String getErrorMessage() {
        return this.errorMsg;
    }

    private void setErrorMessage(String msg) {
        this.errorMsg = msg;
    }

    private boolean isClicked() {
        return this.click;
    }

    private void setClick(boolean click) {
        this.click = click;
    }

    /** Plays the background music
     *
     */
    public void playMusic() {
        if (RPG.getSoundSettings().isPlayingSounds()
                && RPG.getSoundSettings().isBackgroundMusic()) {
            SoundLoader.getMenuBgMusic().play(1f, RPG.getSoundSettings().getBackgroundMusicVolume());
        }
    }

    /** Stops the background music
     *
     */
    public void stopMusic() {
        if (RPG.getSoundSettings().isPlayingSounds()
                && RPG.getSoundSettings().isBackgroundMusic()) {
            SoundLoader.getMenuBgMusic().pause();
        }
    }

    public SubMenu getMenuNew() {
        return this.menuNew;
    }

    public void setMenuNew(SubMenu menuNew) {
        this.menuNew = menuNew;
    }

    public SubMenu getMenuAbout() {
        return this.menuAbout;
    }

    public void setMenuAbout(SubMenu menuAbout) {
        this.menuAbout = menuAbout;
    }

    public SubMenu getMenuSettings() {
        return this.menuSettings;
    }

    public void setMenuSettings(SubMenu menuSettings) {
        this.menuSettings = menuSettings;
    }

    public SubMenu getMenuHelp() {
        return this.menuHelp;
    }

    public void setMenuHelp(SubMenu menuHelp) {
        this.menuHelp = menuHelp;
    }

    private LoadScript getScript() {
        return this.script;
    }

    private void setScript(String fileName) {
        this.script = new LoadScript(fileName);
    }

    private void loadQuests()
            throws SlickException {
        this.setMenuNew(new QuestMenu(this));
        this.setScript(Menu.RESOURCE_PATH + Menu.SCRIPT_NAME);
        this.getScript().runScriptFunction(Menu.FUNC_LOAD, this);
    }

    public void addQuest(String displayName, String dirName)
            throws SlickException {

        dirName = dirName.endsWith("/") ? dirName : (dirName + "/");
        Quest q = new Quest(displayName,Menu.RESOURCE_PATH + dirName);
        this.getRPG().getQuests().add(q);
        QuestTab tab = new QuestTab(q);
        this.getMenuNew().addTab(tab);
    }

    public void setSelectedQuest(Quest quest) {
        this.quest = quest;
    }

    public Quest getSelectedQuest() {
        return this.quest;
    }

    /**
     * @return the rpg
     */
    public RPG getRPG() {
        return this.rpg;
    }

    /**
     * @param rpg the rpg to set
     */
    private void setRPG(RPG rpg) {
        this.rpg = rpg;
    }

}
